package ll1.grammars;

import java.io.BufferedReader;
import java.io.File;

import java.io.FileReader;
import java.io.IOException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LL1Grammar {

	private Map<String, Map<String, String>> table = new HashMap<String, Map<String, String>>();
	private Stack<Character> stack;

	private File file;
	// private List<Production> productions;
	private List<String> terminalSymbols;
	private Map<String, List<String>> productions = new HashMap<String, List<String>>();

	private static final Pattern grammarPattern = Pattern.compile(
			"^([A-Z])(?:\\s([a-z][A-Za-z]*)|$)", Pattern.DOTALL);

	public LL1Grammar() {

	}

	public void getProductions(String fileName) throws IOException, GrammarException {
		this.file = new File(fileName);
		getFromFile();
		if (this.productions.isEmpty()) {
			System.out.println("Gramatica invalida!");
		}
		getTerminalSymbols();
		createTable();
	}

	private void getTerminalSymbols() {
		List<String> lefts = new ArrayList<String>();
		lefts.addAll(productions.keySet());

		terminalSymbols = new ArrayList<String>();

		for (String key : lefts) {
			List<String> rights = productions.get(key);

			for (String right : rights) {

				for (int i = 0; i < right.length(); i++) {
					char symbol = right.charAt(i);
					if (Character.isLowerCase(symbol)) {
						if (!terminalSymbols.contains("" + symbol)) {
							terminalSymbols.add("" + symbol);
						}
					}
				}
			}
		}

	}

	public boolean validate(String str) {
		this.stack = new Stack<Character>();
		int index = 0;
		Character tope;
		Character t;
		boolean ok = true;

		stack.add('#');
		stack.add('S');
		str = str.concat("#");
		// Inspirado en las filminas Parte9.pdf pag.23
		tope = stack.peek();
		t = str.charAt(index);

		do {
			if (terminalSymbols.contains("" + tope)) {
				if (tope.equals(t)) {
					stack.pop();
					index++;
				} else {
					ok = false;
					break;
				}

			} else {
				Map<String, String> row = table.get("" + tope);
				if (row == null) {
					return false;
				}
				String right = row.get("" + t);
				if (right == null) {
					ok = false;
					break;
				}
				if (right.equals("\\")) {

					stack.pop();

				} else {
					stack.pop();
					for (int it = right.length() - 1; it >= 0; it--) {
						stack.add(right.charAt(it));
					}
				}

			}

			tope = stack.peek();
			if (index > str.length()) {
				if (!tope.equals('#')) {
					ok = false;
				}

				break;
			} else {

				t = str.charAt(index);
			}
		} while (!(tope.equals('#') && t.equals('#')));

		return ok;
	}

	private void createTable() {

		List<String> lefts = new ArrayList<String>();
		lefts.addAll(productions.keySet());

		for (String key : lefts) {

			List<String> rights = productions.get(key);

			Map<String, String> stRow = new HashMap<String, String>();

			for (String right : rights) {
				// primero
				char symbol = right.charAt(0);

				if (Character.isLowerCase(symbol)) {
					// es simbolo terminal
					stRow.put("" + symbol, right);
				} else {
					// es no terminal. calculo sus primeros
					for (String prim : getPrimeros(right)) {
						stRow.put(prim, right);
					}
				}
			}

			// Esta negrada se hace para no calcular SIGUIENTES que es
			// imposible.
			if (rights.contains("\\")) {
				for (String ts : terminalSymbols) {
					if (!stRow.containsKey(ts)) {
						stRow.put(ts, "\\");
					}
				}
				stRow.put("#", "\\");
			}

			table.put(key, stRow);

		}
	}

	private List<String> getPrimeros(String right) {
		List<String> list = new ArrayList<String>();

		String snt = "" + right.charAt(0);

		List<String> derivaciones = productions.get(snt);
		if (derivaciones == null) {
			return list;
		}
		for (String derivacion : derivaciones) {
			char symbol = derivacion.charAt(0);
			if (Character.isLowerCase(symbol)) {
				list.add("" + symbol);
			} else {
				list.addAll(getPrimeros("" + symbol));
			}
		}

		return list;
	}

	private void getFromFile() throws IOException, GrammarException {
		BufferedReader br = new BufferedReader(new FileReader(this.file));
		String line;
		
		List<String> derivaciones;

		Matcher m;
		while ((line = br.readLine()) != null) {
			m = grammarPattern.matcher(line.trim());
			if (m.find()) {
				derivaciones = productions.get(m.group(1));
				if (derivaciones == null) {
					derivaciones = new ArrayList<String>();
				}
				if (m.group(2) == null) {
					derivaciones.add("\\");
				} else {
					derivaciones.add(m.group(2));
				}
				productions.put(m.group(1), derivaciones);
				System.out.println(m.group(1) + " -> "
						+ (m.group(2) == null ? "\\" : m.group(2)));
			} else {
				throw new GrammarException("Gramatica Invalida");
			}
		}

	}

}
